import tkinter as tk
import re
from src.globals import g_ic_debugger
from tkinter import messagebox
import sys
import traceback
from src.utils.utils import executor_pool_exception_callback
from src.utils.voice import VoicePlayer
import json
from concurrent.futures import ThreadPoolExecutor
from src.utils.loggers import dev_logger, record_logger, prod_logger
import time
from src.types_pydantic.common_types import AlltestReturnType
from abc import ABC, abstractmethod


class TestView_UI_Base(ABC):
    def __init__(self, rooter):
        self.ROOTER = rooter
        self.W_mainWindow = rooter.W_mainWindow
        self.UDS_SERVE_FUNC = rooter.UDS_SERVE_FUNC
        self.VoicePlayer = VoicePlayer
        self.FUNC_LIB_DICT = rooter.FUNC_LIB_DICT

        # 测试单元的位置信息，与configData中的pos对应
        self.unittest_dict = {}
        # 按钮和测试反馈信息字典，放在init中程序会给予提示
        self.userWindowFrame2_1Btn = {}
        self.userWindowFrame2_1TestResMsg = {}
        # 车型配置的按钮列表
        self.userWindowFrame3_Btn = {}
        self.userWindowFrame3_Btn_Func = {}

        # 是否循环保持
        self.cgw_uds_3e_cycle = False
        self.pool = ThreadPoolExecutor(max_workers=1)
        self.pool_userWindowFrame2_1Btn = ThreadPoolExecutor(
            max_workers=1, thread_name_prefix='userWindowFrame2_1Btn')
        # 初始操作主窗口
        self.initMaster()
        # 当前窗口实例
        self.initCurWindow()

    def initMaster(self):
        # 隐藏主窗口
        self.W_mainWindow.withdraw()

    def initCurWindow(self):
        self.curWindow = tk.Toplevel(self.W_mainWindow)
        self.curWindow.geometry('1000x800+600+200')

        self.initUserWindowFrame1()
        self.initUserWindowFrame2()
        self.initUserWindowFrame3()
        self.initUserWindowFrame6()

        # 启动程序时让vin码输入框自动获取焦点
        self.curVinCodeEntry.focus_set()
        pass

    def initUserWindowFrame1(self):
        '''
            VIN 输入框Frame
        '''

        # 本地函数
        if 1:
            # [x]: 后期改为扫码获取值,与OBD读取值得比对
            def validateVinCode():
                # Vin码输入框验证函数
                curVin = self.curVinCode.get()
                # print(f'validate VinCode {curVin}')

                # [x]: 待开启VIN匹配功能
                pat = re.compile(r'^LNB\w{8}\d{6}$')
                if pat.match(curVin):
                    # if len(curVin) > 4:

                    if 1:
                        for pos in self.unittest_dict:
                            self.userWindowFrame2_1Btn[pos].configure(
                                state=tk.NORMAL)

                        for btn in self.userWindowFrame3_Btn.values():
                            # g_ic_debugger(btn)
                            btn.configure(state=tk.NORMAL)
                        return True

                else:
                    return False

            def validateVinCodeFailed():

                for pos in self.unittest_dict:
                    self.userWindowFrame2_1Btn[pos].configure(
                        state=tk.DISABLED)

                messagebox.showwarning('VIN码验证警告', 'VIN码验证不通过，请核对输入的VIN码是否正确？')
                # print('validate failed...')
                pass

            def curVinCodeGet():
                # 清理输入框内容，获取焦点
                self.curVinCodeEntry.delete(0, tk.END)
                self.curVinCodeEntry.focus_set()

                def get_vin():
                    '''
                        读取车身vin，并写入到entry中
                    '''
                    if self.configData is not None:
                        srcAddr = int(self.configData.get(
                            'get_vin_ecu').get('srcAddr'), 16)
                        dstAddr = int(self.configData.get(
                            'get_vin_ecu').get('dstAddr'), 16)

                    else:
                        srcAddr = 0x716
                        dstAddr = 0x796

                    try:
                        # [ ]: 此处srcAddr和dstAddr 不能写死，要根据测试的车型
                        vin_response = self.UDS_SERVE_FUNC.serve_22_f190(
                            srcAddr=srcAddr, dstAddr=dstAddr)
                        g_ic_debugger(vin_response)
                        vin_list = vin_response.response.param[2:]
                        vin_str = "".join(
                            [f"{chr(item)}" for item in vin_list])
                        # [ ] 生产环境中, vin_response那行要输入正确的 srcAddr  和  dstAddr 参数
                        print(f'[file:{__file__}]=>[line:{
                            (sys._getframe().f_lineno)}]==>{vin_str}')

                        self.curVinCode.set(vin_str)
                        self.curVinCodeEntry.configure(state='readonly')

                    except:
                        messagebox.showwarning('读取vin出错了')
                        print(f'[file:{__file__}]=>[line:{(sys._getframe().f_lineno)}]==>>{
                              traceback.format_exc()}')

                get_vin()

                # 设置按钮使能
                # TODO: 后期将几个测试按钮的使能操作打包起来,或者用容器装起来,遍历实现可扩展性.
                for pos in self.unittest_dict:
                    self.userWindowFrame2_1Btn[pos].configure(
                        state=tk.DISABLED)

                for btn in self.userWindowFrame3_Btn.values():
                    g_ic_debugger(btn)
                    btn.configure(state=tk.DISABLED)

                # [ ] 待确认,重置后是否把展示状态复位?
                for wediget in self.userWindowFrame2_1TestResMsg.values():
                    # print('wdiget:', wediget)
                    wediget.configure(text='待检测...', foreground='gray')
                    # wediget.configure(foreground='gray')

        self.userWindowFrame1 = tk.Frame(self.curWindow, relief='groove')
        self.userWindowFrame1.grid(
            row=1, column=1, columnspan=5, padx=20, pady=20)
        self.curVinCodeLabel = tk.Label(self.userWindowFrame1, text='读取当前VIN码')
        self.curVinCodeLabel.grid(row=1, column=1, padx=10, pady=10)
        self.curVinCode = tk.StringVar()
        self.curVinCode.set('LNB12341234123412')
        self.curVinCodeEntry = tk.Entry(self.userWindowFrame1, textvariable=self.curVinCode,
                                        validate='focusout', validatecommand=validateVinCode, invalidcommand=validateVinCodeFailed)
        self.curVinCodeEntry.grid(row=1, column=2, padx=10, pady=10)
        self.curVinCodeConfirmBtn = tk.Button(
            self.userWindowFrame1, text='确认', command=validateVinCode)
        self.curVinCodeConfirmBtn.grid(row=1, column=3, padx=10, pady=10)
        self.curVinCodeResetBtn = tk.Button(
            self.userWindowFrame1, text='读取车辆VIN', command=curVinCodeGet)
        self.curVinCodeResetBtn.grid(row=1, column=4, padx=10, pady=10)

    def initUserWindowFrame2(self):
        '''
            单个座椅测量按钮
        '''
        self.unittest_dict = {}
        # 将库中的位置按钮对应的测试函数，直接拿过来用，与对应的button关联起来

        # 此处的seatUnittest和self.masterUI.batc_uds_can_unitTab.can_unitTestmodelDict形成映射关系，通过pos
        # 设定的时候要注意选择unittest_dict的键值

        for item in self.configData['unit_test']:

            btn_pos = item['btn_pos']

            from src.types_pydantic.config_types import TestView_Unit_Type
            self.unittest_dict[btn_pos] = TestView_Unit_Type(**item)
            # g_ic_debugger(self.unittest_dict[btn_pos])

        self.userWindowFrame2 = tk.Frame(self.curWindow)
        self.userWindowFrame2.grid(
            row=2, column=1, columnspan=5, padx=20, pady=20)
        if 1:
            self.userWindowFrame2_1 = tk.Frame(
                self.userWindowFrame2, relief='groove')
            self.userWindowFrame2_1.grid(row=1, column=1)
            # TODO: 后期改成遍历循环模式
            if 1:

                for _, testunit in self.unittest_dict.items():
                    row = int(testunit.btn_pos[0])
                    col = int(testunit.btn_pos[1])

                    # g_ic_debugger(testunit)
                    self.userWindowFrame2_1Btn[testunit.btn_pos] = tk.Button(
                        self.userWindowFrame2_1, state=tk.DISABLED, text=testunit.name, command=self.unittest(testunit))
                    self.userWindowFrame2_1Btn[testunit.btn_pos].grid(
                        row=row, column=col*2, padx=10, pady=10)

                    self.userWindowFrame2_1TestResMsg[testunit.btn_pos] = tk.Message(
                        self.userWindowFrame2_1, width=100, foreground='gray', text=f"{testunit.name}测试结果")
                    self.userWindowFrame2_1TestResMsg[testunit.btn_pos].grid(
                        row=row, column=col*2+1, padx=10, pady=10)

    def initUserWindowFrame3(self):
        '''
            一键快检Frame
        '''

        # 增加一键快检功能
        self.userWindowFrame3 = tk.Frame(self.curWindow, relief='groove')
        self.userWindowFrame3.grid(
            row=3, column=1, columnspan=5, padx=20, pady=20)

        # 本地函数
        if 1:
            # FIXME: 一键快检功能函数
            # [ ] 待确认几个按钮同时点击会否有异步运行问题
            def test_peizhi(peizhi):
                def warpper():
                    g_ic_debugger(peizhi)
                    pos_selected = self.configData['level_option'][peizhi]
                    g_ic_debugger(pos_selected)
                    for pos in pos_selected:
                        # 用一个单独线程来跑测试程序，强制各个子单元的测试，按顺序执行
                        res = self.pool_userWindowFrame2_1Btn.submit(
                            self.userWindowFrame2_1Btn[pos].invoke)
                        # 跟踪错误信息
                        res.add_done_callback(executor_pool_exception_callback)
                        # self.userWindowFrame2_1Btn[pos].invoke()

                return warpper

            def testLV3():

                # pos_selected = ['01', '02', '03']
                pos_selected = self.configData['level_option']['LV3']
                for pos in pos_selected:
                    # 用一个单独线程来跑测试程序，强制各个子单元的测试，按顺序执行
                    res = self.pool_userWindowFrame2_1Btn.submit(
                        self.userWindowFrame2_1Btn[pos].invoke)
                    # 跟踪错误信息
                    res.add_done_callback(executor_pool_exception_callback)
                    # self.userWindowFrame2_1Btn[pos].invoke()

            def testLV4():
                pos_selected = self.configData['level_option']['LV4']
                for pos in pos_selected:
                    # 用一个单独线程来跑测试程序，强制各个子单元的测试，按顺序执行
                    res = self.pool_userWindowFrame2_1Btn.submit(
                        self.userWindowFrame2_1Btn[pos].invoke)
                    # 跟踪错误信息
                    res.add_done_callback(executor_pool_exception_callback)

            def testLV5():
                pos_selected = self.configData['level_option']['LV5']
                for pos in pos_selected:
                    # 用一个单独线程来跑测试程序，强制各个子单元的测试，按顺序执行
                    res = self.pool_userWindowFrame2_1Btn.submit(
                        self.userWindowFrame2_1Btn[pos].invoke)
                    # 跟踪错误信息
                    res.add_done_callback(executor_pool_exception_callback)
        # 按钮布局
        if 1:
            self.userWindowFrame3_Label1 = tk.Label(
                self.userWindowFrame3, text='一键快检')
            self.userWindowFrame3_Label1.grid(
                row=1, column=1, columnspan=3, padx=20, pady=20)

            row = 2
            column = 1
            g_ic_debugger(self.configData['level_option'])
            for peizhi in self.configData['level_option']:
                self.userWindowFrame3_Btn_Func[peizhi] = test_peizhi(
                    peizhi)
                self.userWindowFrame3_Btn[peizhi] = tk.Button(
                    self.userWindowFrame3, state=tk.DISABLED, text=peizhi, command=self.userWindowFrame3_Btn_Func[peizhi])
                self.userWindowFrame3_Btn[peizhi].grid(
                    row=row, column=column, padx=20, pady=20)
                # 一行排布
                column += 1

    def initUserWindowFrame6(self):
        '''
            运行信息展示
        '''

        # 测试结果显示框
        self.userWindowFrame6 = tk.Frame(self.curWindow)
        self.userWindowFrame6.grid(
            row=6, column=1, columnspan=5, padx=20, pady=20)
        if 1:

            self.userWindowFrame6recordListboxScroll = tk.Scrollbar(
                self.userWindowFrame6, orient=tk.VERTICAL)
            self.userWindowFrame6recordListboxScroll.grid(
                row=2, column=11, sticky="ns")
            self.userWindowFrame6recordListbox = tk.Listbox(
                self.userWindowFrame6,
                font=("Arial", 12),
                height=10,
                width=80,
                yscrollcommand=self.userWindowFrame6recordListboxScroll.set,
            )
            self.userWindowFrame6recordListbox.grid(
                row=2, rowspan=2, column=0, columnspan=11, sticky="nw")
            self.userWindowFrame6recordListboxScroll.config(
                command=self.userWindowFrame6recordListbox.yview)
            self.userWindowFrame6recordClearBtn = tk.Button(
                self.userWindowFrame6,
                text="清空测试记录",
                command=lambda: self.userWindowFrame6recordListbox.delete(
                    0, tk.END),
            )
            self.userWindowFrame6recordClearBtn.grid(
                row=2, column=12, padx=2, pady=1)
            self.userWindowFrame6recordmasterUIBtn = tk.Button(
                self.userWindowFrame6,
                text="显示主界面",
                command=lambda: self.W_mainWindow.deiconify()
            )
            self.userWindowFrame6recordmasterUIBtn.grid(
                row=3, column=12, padx=2, pady=1)

    @abstractmethod
    def unittest(self, unittest_data):
        '''
            该函数主要是对unittestcommand函数的返回值进行处理，因为unittestcommand是异步处理，所以对其return结果进行回调函数的设定处理
            callback 进行返回函数的处理
        '''
        # dev_logger.debug(f'seatUnittest running...{posId}.........')

        unittestData = self.FUNC_LIB_DICT[unittest_data.func_lib].pos_data_configs[
            unittest_data.func_pos]
        # g_ic_debugger(unittestData)
        unitName = unittestData['name']
        # unitName = self.unittest_dict[pos].name
        # dev_logger.debug(f'seatUnittest running...{
        #  pos}....unitName:{unitName}.....')

        def callback(worker):

            record_logger.debug(f'seatUnittest  callback running ...{
                unittest_data.func_pos}.........')
            if 1:
                ret = worker.result()
                resFlag = ret.resFlag
                resString = ret.resString
                record_logger.debug(f'seatUnittest  callback running ...{
                    unittest_data.func_pos}...{resFlag, resString}.........')

                # userwindow中的信息展示
                self.userWindowFrame2_1TestResMsg[unittest_data.btn_pos].configure(
                    text=resString)
                foreground = 'green' if resFlag else 'red'
                self.userWindowFrame2_1TestResMsg[unittest_data.btn_pos].configure(
                    foreground=foreground)

                # 保留测试record信息
                recordStringLogger = f'VIN,{self.curVinCode.get():>17},{
                    resString}'
                record_logger.info(recordStringLogger)

                # 记录到生产日志中
                prod_logger.info(recordStringLogger)

                # 声音播报,播放 合格 不合格 声音提示
                if resFlag:
                    self.VoicePlayer.say(f'{unitName},合格')
                    # 这里增加每个pos模块都会弹出的提示框，不合理，需要修改
                    # messageBox.showinfo('提示', f'{unitName},合格')

                else:
                    self.VoicePlayer.say(f'{unitName},不合格')
                    # messageBox.showerror('提示', f'{unitName},不合格')

                # self.VoicePlayer.say(f'{unitName},{resString}')

                # 增加当前时间
                recordStringListobx = f'{time.strftime(
                    "%Y-%m-%d %H:%M:%S", time.localtime())}--{unitName}---{recordStringLogger}'
                # print('----',recordStr)
                self.userWindowFrame6recordListbox.insert(
                    'end', recordStringListobx)
                # 设置条目颜色
                if not resFlag:
                    self.userWindowFrame6recordListbox.itemconfig(
                        'end', background='pink')
                self.userWindowFrame6recordListbox.see(
                    self.userWindowFrame6recordListbox.size())
                # pass

        def wrapper():
            dev_logger.debug(f'seatUnittest  wrapper running ...{
                             unittest_data.func_pos}.........')
            # 点击按钮的测试结果结构是 resFlag,resString
            testResult = self.unitTestCommand(unittest_data)()
            testResult.add_done_callback(callback)

        return wrapper

    # [ ] 待完善批量测试逻辑
    @abstractmethod
    def unitTestCommand(self, unittest_data):
        '''
            实现某个具体单元的alltest功能，并返回alltest的结果，将结果返回给seatUnittest使用
        '''
        def wrapper():
            def inner():
                # 设定最大重测次数
                test_counts = 1
                for i in range(test_counts):
                    # testfuncDict里面放的是单元测试的构造函数，运行时候会生产单元测试的面板,frame00就是实例
                    # 后续要将frame00窗口隐藏，以提供更好的体验。
                    # frame00 = self.unittest_dict[pos].func()
                    # frame00 = self.masterUI.batc_uds_can_unitTab.can_unitTab_UNIT_FUN_DICT[pos](
                    # )
                    frame00 = self.FUNC_LIB_DICT[unittest_data.func_lib].LIB_UNIT_FUN_DICT[unittest_data.func_pos](
                    )

                    g_ic_debugger(frame00)

                    # TODO: frame00.allTest就是运行当前单元测试的批量测试， 也可以用btn的invoke模拟按钮被按下来实现(该方法暂未验证)
                    # 此处把allTest的return值拿到，用来在主界面显示
                    frame00.beforeTest()
                    time.sleep(1)
                    testResult_raw = frame00.allTest()

                    # 拿到alltest的结果
                    testResult: AlltestReturnType = testResult_raw.result()
                    resFlag = testResult.resFlag
                    resString = testResult.resString

                    # 测试完成后隐藏窗口
                    frame00.withdraw()

                    if resFlag:
                        return testResult

                # 如果3个循环都走完了，仍然没有True
                return AlltestReturnType(resFlag=False, resString='测试失败')

            # FIXME: 此处用一个单独的线程来跑，否则result()函数会把主线程阻塞，导致无法正常工作
            res = self.pool.submit(inner)
            res.add_done_callback(executor_pool_exception_callback)
            return res

        return wrapper


class Batc_TestView(TestView_UI_Base):
    def __init__(self, rooter):
        with open('./config/test_views/batc_b60v_testview.json', 'r', encoding='utf-8') as f:
            self.configData = json.load(f)

        # 因为父类初始化会用到self.configData，所以要先提供该变量，然后才可以调用父类的初始化函数
        super().__init__(rooter)

        self.curWindow.title('BATC_B60V_控制器测试操作台')

    def unittest(self, unittest_data):
        return super().unittest(unittest_data)

    def unitTestCommand(self, unittest_data):
        return super().unitTestCommand(unittest_data)


class Bggc_Seat_TestView(TestView_UI_Base):
    def __init__(self, rooter):
        with open('./config/test_views/bggc_seat_testview.json', 'r', encoding='utf-8') as f:
            self.configData = json.load(f)

        # 因为父类初始化会用到self.configData，所以要先提供该变量，然后才可以调用父类的初始化函数
        super().__init__(rooter)

        self.curWindow.title('Bggc_C62X 座椅测试操作台')

    def unittest(self, unittest_data):
        return super().unittest(unittest_data)

    def unitTestCommand(self, unittest_data):
        return super().unitTestCommand(unittest_data)
